﻿using System;
using System.Collections.Generic;
using System.Linq;
using Elderos.Utils;
using Elderos.Utils.Extensions;
using Elderos.Utils.Logging;

namespace Elderos.Highlight.Search
{
    public class SearchResultAdapter : ISearchResultAdapter
    {
        public List<Position> ProcessPositions(string text, List<Position> searchResults)
        {
            Logger.Assert(() => searchResults != null, "Result array is null.", typeof(NullReferenceException));
            if (searchResults.Count < 1) return searchResults;

            var newResults = RemoveNonBorderEntries(text, searchResults);

            newResults = ProcessNestedIntervals(newResults);

            newResults = ProcessIntersectingIntervals(newResults, text);

            return newResults;
        }

        /// <summary>
        /// Removes all entries, where entry start or end lies in the middle of word
        /// </summary>
        /// <param name="text"></param>
        /// <param name="searchResults"></param>
        /// <returns></returns>
        private static List<Position> RemoveNonBorderEntries(string text, IEnumerable<Position> searchResults)
        {
            List<Position> newResults =
                (from sr in searchResults
                 let rightChar = text.Length > sr.CharInterval.Last + 1 ? (char?)text[sr.CharInterval.Last + 1] : (char?)null
                 where !(rightChar.HasValue && char.IsLetterOrDigit(rightChar.Value))
                 let leftChar = sr.CharInterval.First - 1 >= 0 ? (char?)text[sr.CharInterval.First - 1] : (char?)null
                 where !(leftChar.HasValue && char.IsLetterOrDigit(leftChar.Value))
                 select sr).ToList();

            return newResults;
        }

        private static List<Position> ProcessIntersectingIntervals(List<Position> searchResults, string text) 
        {
            searchResults.Sort((x, y) => x.CharInterval.First - y.CharInterval.First);

            var newResults = new List<Position>();
            while (true)
            {
                var current = searchResults.FirstOrDefault();
                if (current == default(Position))
                    break;

                searchResults.Remove(current);

                var intersection =
                    searchResults.FirstOrDefault(x => x.CharInterval.IsIntersection(current.CharInterval));

                if (intersection == default(Position))
                {
                    newResults.Add(current);
                    continue;
                }

                searchResults.Remove(intersection);

                var union = current.CharInterval.Combine(intersection.CharInterval);

                var newPos =  Position.CreateFake(union.First, union.Length, text.Substring(union.First, union.Length));
                newPos.Subpositions.Add(current);
                newPos.Subpositions.Add(intersection);

                newResults.Add(newPos);
            }
            return newResults;
        }

        private static List<Position> ProcessNestedIntervals(List<Position> searchResults) {
            while (true)
            {
                int was = searchResults.Count;
                var nestedIntervals = GetNestedIntervals(searchResults);

                foreach (var nestedInterval in nestedIntervals)
                {
                    var interval = nestedInterval.Item1;
                    var containedBy = nestedInterval.Item2;

                    if (containedBy == null) continue;
                    containedBy.Subpositions.Add(interval);
                    searchResults.Remove(interval);
                }

                int becomes = searchResults.Count;
                if (was <= becomes) break;
            }
            return searchResults;
        }

        private static List<Tuple<Position, Position>> GetNestedIntervals(List<Position> intervals)
        {
            var result = new List<Tuple<Position, Position>>();
            for (int i = 0; i < intervals.Count; i++)
            {
                var interval = intervals[i];
                Position containedBy = null;
                for (int j = 0; j < intervals.Count; j++)
                {
                    if (i == j) continue;
                    var pretender = intervals[j];

                    if (pretender.CharInterval.Contains(interval.CharInterval))
                    {
                        if (containedBy == null || pretender.CharInterval.Length < containedBy.CharInterval.Length)
                            containedBy = pretender;
                    }
                }
                result.Add(new Tuple<Position, Position>(interval, containedBy));
            }

            return result;
        }

        public void SetTokenInfo(IEnumerable<Position> infos, Token[] tokens)
        {
            var tempEntities = new Dictionary<Position, int>();
            int textIndex = 0;
            for (int i = 0; i < tokens.Length; i++)
            {
                int tokenEnd = textIndex + tokens[i].Length - 1;
                var interval = new Interval<int>(textIndex, tokenEnd);
                IEnumerable<Position> entities = infos.Where(x => interval.Contains(x.CharInterval.First));
                tempEntities.AddRange(entities.Select(x => new KeyValuePair<Position, int>(x, i)));

                Dictionary<Position, Interval<int>> entityWithIntervals =
                    tempEntities.Where(x => interval.Contains(x.Key.CharInterval.Last))
                                .ToDictionary(x => x.Key, y => new Interval<int>(y.Value, i));

                foreach (var entityWithInterval in entityWithIntervals)
                {
                    entityWithInterval.Key.TokenInterval = entityWithInterval.Value;
                    tempEntities.Remove(entityWithInterval.Key);
                }
                textIndex = tokenEnd + 1;
            }
        }
    }
}
